#include "JSCScript.h"

#include <unistd.h>
#include "kit.h"

namespace phnq
{
	namespace uth
	{
		namespace impl
		{
			static phnq::kit::log::Logger *log = phnq::kit::log::Logger::get("phnq::uth::impl::JSCScript");

			static JSValueRef uth_jsc(UTHValue *v, JSContextRef ctx);
			static JSValueRef uth_jsc_object(UTHObject *o, JSContextRef ctx);
			static JSValueRef uth_jsc_array(UTHArray *a, JSContextRef ctx);
			static JSValueRef uth_jsc_string(UTHString *s, JSContextRef ctx);
			static JSValueRef uth_jsc_number(UTHNumber *n, JSContextRef ctx);
			static JSValueRef uth_jsc_function(UTHFunction *fn, JSContextRef ctx);
	
			static UTHValue* jsc_uth(JSValueRef val, JSContextRef ctx);
			static UTHValue* jsc_uth_string(JSValueRef val, JSContextRef ctx);
			static UTHValue* jsc_uth_number(JSValueRef val, JSContextRef ctx);
			static UTHValue* jsc_uth_object(JSValueRef val, JSContextRef ctx);
			static UTHValue* jsc_uth_array(size_t argumentCount, const JSValueRef arguments[], JSContextRef ctx);
	
			static JSValueRef generic_function_callback(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);
			static void printException(JSContextRef ctx, JSValueRef exception);

			JSCScript::JSCScript(const char *code) : UTHScript()
			{
				this->code = (char*)malloc(strlen(code) + 1);
				strcpy(this->code, code);
			}

			JSCScript::~JSCScript()
			{
				JSStringRelease(script);
				JSGlobalContextRelease(context);
				free(code);
			}

			void JSCScript::compile()
			{
				log->info("compiling script");
				
				JSValueRef exception = 0;
				
				this->script = JSStringCreateWithUTF8CString(code);
				this->context = JSGlobalContextCreateInGroup(NULL, NULL);
						
				JSObjectRef globalObject = JSContextGetGlobalObject(this->context);
						
				vector<string> keys = globals->getKeys();
				int len = keys.size();
				for(int i=0; i<len; i++)
				{
					string key = keys[i];
					UTHValue *val= globals->get(key);
						
					JSStringRef jscKey = JSStringCreateWithUTF8CString(key.c_str());
					JSValueRef jscVal = uth_jsc(val, this->context);
					JSObjectSetProperty(this->context, globalObject, jscKey, jscVal, kJSPropertyAttributeNone, &exception);
						
					if(exception)
						printException(context, exception);
							
					JSStringRelease(jscKey);
				}
			}
	
			UTHValue* JSCScript::doExecute()
			{
				JSValueRef result;
				JSValueRef exception = 0;
		
				result = JSEvaluateScript(context, script, NULL, NULL, 1, &exception);
				if(exception)
				{
					printException(context, exception);
					return UTHUndefined::getInstance();
				}
		
				return jsc_uth(result, context);
			}
	
			JSValueRef uth_jsc(UTHValue *v, JSContextRef ctx)
			{
				if(v == NULL)
					return JSValueMakeUndefined(ctx);
		
				if(v->isString())
					return uth_jsc_string((UTHString*)v, ctx);
				else if(v->isNumber())
					return uth_jsc_number((UTHNumber*)v, ctx);
				else if(v->isFunction())
					return uth_jsc_function((UTHFunction*)v, ctx);
				else if(v->isArray())
					return uth_jsc_array((UTHArray*)v, ctx);
				else if(v->isObject())
					return uth_jsc_object((UTHObject*)v, ctx);
				else
					return JSValueMakeUndefined(ctx);
			}
	
			JSValueRef uth_jsc_object(UTHObject *obj, JSContextRef ctx)
			{
				JSValueRef exception = 0;

				JSObjectRef jscObj = JSObjectMake(ctx, NULL, NULL);
		
				vector<string> keys = obj->getKeys();
				int len = keys.size();
				for(int i=0; i<len; i++)
				{
					string key = keys[i];
					UTHValue *val= obj->get(key);
			
					JSStringRef jscKey = JSStringCreateWithUTF8CString(key.c_str());
					JSValueRef jscVal = uth_jsc(val, ctx);
			
					JSObjectSetProperty(ctx, jscObj, jscKey, jscVal, kJSPropertyAttributeNone, &exception);

					if(exception)
						printException(ctx, exception);
			
					JSStringRelease(jscKey);
				}
		
				return jscObj;
			}
	
			JSValueRef uth_jsc_array(UTHArray *arr, JSContextRef ctx)
			{
				JSValueRef exception = 0;

				int len = arr->length();
				JSValueRef items[len];
				for(int i=0; i<len; i++)
				{
					UTHValue* val = arr->get(i);
					JSValueRef jscVal = uth_jsc(val, ctx);
					items[i] = jscVal;
				}
		
				JSObjectRef jscArr = JSObjectMakeArray(ctx, len, items, &exception);

				if(exception)
					printException(ctx, exception);
		
				return jscArr;
			}
	
			JSValueRef uth_jsc_string(UTHString *s, JSContextRef ctx)
			{
				return JSValueMakeString(ctx, JSStringCreateWithUTF8CString(s->getChars()));
			}
	
			JSValueRef uth_jsc_number(UTHNumber *n, JSContextRef ctx)
			{
				return JSValueMakeNumber(ctx, n->doubleValue());
			}

			JSValueRef uth_jsc_function(UTHFunction *fn, JSContextRef ctx)
			{
				JSValueRef exception = 0;

				JSObjectRef jscFn = JSObjectMakeFunctionWithCallback(ctx, NULL, generic_function_callback);
		
				/*
				*	Storing the address of the UTHFunction instance as a read-only property
				*	on the underlying function object.  JSObjectGetPrivate/JSObjectSetPrivate
				*	does not appear to be supported by functions.
				*/
				JSStringRef uthRefKey = JSStringCreateWithUTF8CString("_uth_ref");
		
				JSValueRef uthRefNum = JSValueMakeNumber(ctx, (double)(long)fn);
				JSObjectSetProperty(ctx, jscFn, uthRefKey, uthRefNum, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete, &exception);
		
				if(exception)
					printException(ctx, exception);
		
				JSStringRelease(uthRefKey);
		
				return jscFn;
			}

			static JSValueRef generic_function_callback(JSContextRef ctx, JSObjectRef fnObj, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
			{
				JSValueRef ex = 0;
		
				JSStringRef uthRefKey = JSStringCreateWithUTF8CString("_uth_ref");
		
				JSValueRef uthRefVal = JSObjectGetProperty(ctx, fnObj, uthRefKey, &ex);
		
				if(ex)
					printException(ctx, ex);
		
				JSStringRelease(uthRefKey);
		
				long uthRef = (long)JSValueToNumber(ctx, uthRefVal, &ex);
				if(ex)
					printException(ctx, ex);
		
				UTHFunction *fn = (UTHFunction*)uthRef;
		
				if(fn)
				{
					UTHArray *args = (UTHArray*)jsc_uth_array(argumentCount, arguments, ctx);
					UTHValue *result = fn->invoke(args);
					JSValueRef val = uth_jsc(result, ctx);
					result->release();
					args->release();
					return val;
				}
			
				return JSValueMakeUndefined(ctx);
			}
	
	
			static UTHValue* jsc_uth(JSValueRef val, JSContextRef ctx)
			{
				switch(JSValueGetType(ctx, val))
				{
					case kJSTypeUndefined:
						return UTHUndefined::getInstance();
						break;
			
					case kJSTypeNull:
						return UTHNull::getInstance();
						break;

					case kJSTypeNumber:
						return jsc_uth_number(val, ctx);
						break;

					case kJSTypeString:
						return jsc_uth_string(val, ctx);
						break;

					case kJSTypeObject:
						return jsc_uth_object(val, ctx);
						break;
				}
			}
	
			static UTHValue* jsc_uth_string(JSValueRef val, JSContextRef ctx)
			{
				JSValueRef exception = 0;
		
				JSStringRef jscStr = JSValueToStringCopy(ctx, val, &exception);
				if(exception)
				{
					printException(ctx, exception);
					return UTHNull::getInstance();
				}
		
				int len = JSStringGetMaximumUTF8CStringSize(jscStr);
				char str[len+1];
				JSStringGetUTF8CString(jscStr, str, len);
				JSStringRelease(jscStr);
		
				return new UTHString(str);
			}
	
			static UTHValue* jsc_uth_number(JSValueRef val, JSContextRef ctx)
			{
				JSValueRef exception = 0;

				double num = JSValueToNumber(ctx, val, 0);
				if(exception)
				{
					printException(ctx, exception);
					return UTHNull::getInstance();
				}
		
				return new UTHNumber(num);
			}
	
			static UTHValue* jsc_uth_object(JSValueRef val, JSContextRef ctx)
			{
				JSValueRef exception = 0;

				JSObjectRef objRef = JSValueToObject(ctx, val, &exception);
				if(exception)
				{
					printException(ctx, exception);
					return UTHNull::getInstance();
				}

				JSObjectRef globalObject = JSContextGetGlobalObject(ctx);
		
				JSStringRef arrayStr = JSStringCreateWithUTF8CString("Array");
				JSObjectRef arrayConstructor = JSValueToObject(ctx, JSObjectGetProperty(ctx, globalObject, arrayStr, NULL), NULL);
				JSStringRelease(arrayStr);
		
				if(JSValueIsInstanceOfConstructor(ctx, val, arrayConstructor, 0))
				{
					UTHArray *arr = new UTHArray();
					size_t num = JSPropertyNameArrayGetCount(JSObjectCopyPropertyNames(ctx, objRef));
					for(size_t i=0; i<num; i++)
					{
						JSValueRef itemRef = JSObjectGetPropertyAtIndex(ctx, objRef, i, 0);
						UTHValue *item = jsc_uth(itemRef, ctx);
						arr->add(item);
						item->release();
					}
					return arr;
				}
				else
				{
					UTHObject *obj = new UTHObject();
					JSPropertyNameArrayRef propNames = JSObjectCopyPropertyNames(ctx, objRef);
					size_t num = JSPropertyNameArrayGetCount(propNames);
					for(size_t i=0; i<num; i++)
					{
						JSStringRef propNameRef = JSPropertyNameArrayGetNameAtIndex(propNames, i);

						int len = JSStringGetMaximumUTF8CStringSize(propNameRef);
						char propName[len+1];
						JSStringGetUTF8CString(propNameRef, propName, len);
				
						JSValueRef propValRef = JSObjectGetProperty(ctx, objRef, propNameRef, &exception);

						if(exception)
						{
							printException(ctx, exception);
						}
						else
						{
							UTHValue *val = jsc_uth(propValRef, ctx);
							obj->set(propName, val);
							val->release();
						}

						JSStringRelease(propNameRef);
					}
					return obj;
				}
			}
	
			static UTHValue* jsc_uth_array(size_t argumentCount, const JSValueRef arguments[], JSContextRef ctx)
			{
				UTHArray *arr = new UTHArray();
		
				for(size_t i=0; i<argumentCount; i++)
				{
					arr->add(jsc_uth(arguments[i], ctx));
				}
		
				return arr;
			}
	
			void printException(JSContextRef ctx, JSValueRef exception)
			{
				if(exception)
				{
					int len;
			
					JSStringRef nameRef = JSValueToStringCopy(ctx, JSObjectGetProperty(ctx, (JSObjectRef)exception, JSStringCreateWithUTF8CString("name"), 0), 0);
					JSStringRef msgRef = JSValueToStringCopy(ctx, JSObjectGetProperty(ctx, (JSObjectRef)exception, JSStringCreateWithUTF8CString("message"), 0), 0);
					double line = JSValueToNumber(ctx, JSObjectGetProperty(ctx, (JSObjectRef)exception, JSStringCreateWithUTF8CString("line"), 0), 0);
					double sourceId = JSValueToNumber(ctx, JSObjectGetProperty(ctx, (JSObjectRef)exception, JSStringCreateWithUTF8CString("sourceId"), 0), 0);
			
					len = JSStringGetMaximumUTF8CStringSize(msgRef);
					char msg[len+1];
					JSStringGetUTF8CString(msgRef, msg, len);
			
					len = JSStringGetMaximumUTF8CStringSize(nameRef);
					char name[len+1];
					JSStringGetUTF8CString(nameRef, name, len);
			
					cout << "[" << name << ":" << line << "] " << msg << endl;
				}
			}
		}
	}
}
